/**
 * Copyright (c) suzuki ryoutoku
 * See this code regarding the Apache License Version 2.0
 * @author suzuki ryoutoku
 */

//=============================================================================
//	Pre processor
//=============================================================================
#include "GlobalParameter.h"
#include "CallBack.h"
#include <cstring>
#include <cstdlib>
#include <gdk/gdkx.h>
#include <gtk/gtkgl.h>

//=============================================================================
//	GlobalParameter's memthod
//=============================================================================
/**
 * @author suzuki ryoutoku
 * @date 2007-12-19
 * @version 1.0
 */
GlobalParameter::GlobalParameter()
{
  this->m_main_window = NULL;
  this->_drawarea = NULL;
  this->m_hierarchyview = NULL;
  this->m_attributeview = NULL;
  this->m_symboltable = NULL;
  this->m_rm = NULL;
  this->m_model = NULL;
  this->m_background=NULL;
  this->resetMouseCoordinate();
  this->m_div = 0.01;
  strcpy(_data_directory, getenv("PWD"));// To get current dir => PWD
  sprintf(this->m_home_directory, "%s/.%s",getenv("HOME"), APPLICATION_NAME);
  this->m_keystate=0;
  _strip_start_select=IO_ORDER;
  m_progress=NULL;
  m_progress_dialog=NULL;
  m_strip_counter=0;
  m_fps.setM_state(1);
  _check_selectpoly=NULL;
  _check_selectvert=NULL;
}

/**
 * @author suzuki ryoutoku
 * @date 2007-12-19
 * @version 1.0
 */
GlobalParameter::~GlobalParameter()
{
  this->releaseModelData();
  delete m_rm;
  if( m_background != NULL ) delete m_background;
}

/**
 * @author suzuki ryoutoku
 * @date 2007-12-20
 * @version 1.0
 */
void GlobalParameter::releaseModelData(void)
{
  m_rm->getM_screen(0)->cancelGraphicalCommand(m_model);
  delete this->m_model;
  this->m_model = NULL;
}


/**
 * @author suzuki ryoutoku
 * @date 2007-12-19
 * @version 1.0
 */
void GlobalParameter::resetMouseCoordinate(void)
{
  this->m_before_x=0;
  this->m_before_y=0;
  this->m_before_z=0;
}

/**
 * @param ix -1 in defalut
 * @param iy -1 in defalut
 * @param iz -1 in defalut
 * @author suzuki ryoutoku
 * @date 2007-12-20
 * @version 1.0
 */
void GlobalParameter::substituteMouseCoordinate(gdouble ix,
												gdouble iy,
												gdouble iz)
{
  if( ix != -1 ) this->m_before_x = ix;
  if( iy != -1 ) this->m_before_y = iy;
  if( iz != -1 ) this->m_before_z = iz;
}

/**
 * This method is roler reading texture file used in 3ds file.
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-2-28
 */
bool GlobalParameter::readTextureFile(Reader* reader)
{
  // change directory to being 3ds file 
  chdir(this->_data_directory);

  // Next codes are to load texture file & create image data
  for( unsigned int i = 0; i < reader->get_texfilename()->size(); i++){
	string tmp = reader->get_texfilename()->at(i);
	if( string::npos != tmp.rfind("bmp") ||
		string::npos != tmp.rfind("BMP") ){

	  ImageDataManager::getInstance()->createImageData(tmp);
	}else if( string::npos != tmp.rfind("jpg") ||
			  string::npos != tmp.rfind("JPG") ){
	  BITMAPINFO info;
	  unsigned int* bitpattern;
	  if( true == CreateBitmapFromJpgfile(&info, 
										  &bitpattern, 
										  tmp.c_str()) ){
		ImageData* image = new ImageData(tmp.c_str(),
										 &info,
										 bitpattern);
		ImageDataManager::getInstance()->assignTexId(image);
		delete [] bitpattern;
	  }
	}
  }
  return true;
}

/**
 * Initialize log manager
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-5-22
 */
bool GlobalParameter::initializeaLogManager(void)
{
  char tmp[1024];
  LogManager::GetInstance()->initialize(string(LOG_FILENAME));
  LogManager::GetInstance()->print("Log file of StripExperiment\n");
  return true;
}

/**
 * This method creates default data for application.
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-5-14
 */
bool GlobalParameter::createDefaultData(void)
{
  BITMAPINFO info;
  unsigned int* bitpattern;
  char filename[256];
  sprintf(filename,
		  "%s/data.jpg", 
		  this->m_home_directory);
  
  if( true == CreateBitmapFromJpgfile(&info, &bitpattern, filename) ){
	ImageData* image = new ImageData("data",
									 &info,
									 bitpattern);
	delete [] bitpattern;
	ImageDataManager::getInstance()->assignTexId(image);
  }else{
	cout << "Can not find data.jpg file" << endl;
  }
  setM_model(new Model(true));
  m_model->setTextureID(ImageDataManager::getInstance());
  this->m_rm->getM_screen(0)->registGraphicalCommand(m_model);
  return true;
}

/**
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-5-18
 */
bool GlobalParameter::createBackground(void)
{
  this->setM_background(new Sprite());
  this->getM_background()->setM_rect(0,0,100,100);
  this->getM_background()->setM_color( Color<float>(1.0f,1.0f,1.0f,1.0f) );
  this->m_rm->getM_screen(0)->registGraphicalCommand(this->getM_background());
  return true;
}

/**
 *
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-5-18
 */
bool GlobalParameter::createDrawingString(void)
{
  char tmp[256];
  sprintf(tmp,
		  "face sum = %d node sum = %d", 
		  m_model->get_polygon_sum(),
		  m_model->get_vertex_sum());
  m_shapeinformation.setM_message(tmp);
  m_rm->getM_screen(0)->registGraphicalCommand(&m_shapeinformation);
  sprintf(tmp,"fps = 0");
  m_fps.setM_message(tmp);
  m_fps.setM_line_number(1);
  m_rm->getM_screen(0)->registGraphicalCommand(&m_fps);
  return true;
}

/**
 * Create tree view for setting graphic 
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-5-19
 */
bool GlobalParameter::createAttributeViewWindow(void)
{
  GtkTreeStore* store = gtk_tree_store_new(1, G_TYPE_STRING);
  GtkTreeView* treeview = (GtkTreeView*)gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
  GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
  GtkTreeViewColumn* column = column = gtk_tree_view_column_new();

  gtk_tree_view_column_pack_start(column, renderer, FALSE);
  gtk_tree_view_column_set_attributes(column,
									  renderer,
									  "text",
									  0,
									  NULL);
  g_signal_connect(treeview,"row-activated",
				   G_CALLBACK(cb_tree_item_double_clicked),
				   (gpointer)NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview),column);
  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
  this->initializeAttributeView(store, true);
  this->m_attributeview = treeview;
  
  return true;
}

/**
 * This method creates tree view window
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-5-19
 */
bool GlobalParameter::createHierarchyViewWindow(void)
{
  GtkTreeStore* treestore = gtk_tree_store_new(2,
											   G_TYPE_BOOLEAN,
											   G_TYPE_STRING);
  m_hierarchyview 
	= GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore)));

  GtkCellRenderer* renderer;
  GtkTreeViewColumn* column;

  // toggle
  renderer = gtk_cell_renderer_toggle_new ();
  column = gtk_tree_view_column_new();
  gtk_tree_view_column_set_title(column , "show");
  gtk_tree_view_column_pack_start(column , renderer , FALSE);
  gtk_tree_view_column_set_attributes(column , 
									  renderer ,
									  "active" , 
									  0,
									  NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(m_hierarchyview),
							  column);

  g_signal_connect(renderer , 
				   "toggled" ,
				   G_CALLBACK(cb_status_toggled),
				   m_hierarchyview);
  
  // text
  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new();
  gtk_tree_view_column_set_title(column , "volume name");
  gtk_tree_view_column_pack_start(column , renderer , FALSE);
  gtk_tree_view_column_set_attributes(column , 
									  renderer ,
									  "text" , 
									  1,
									  NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(m_hierarchyview) , 
							  column);

  gtk_tree_view_set_headers_visible(m_hierarchyview,
									FALSE);
  return true;
}


/**
 * This method create Notebook widget inserted two tree view,
 * and lets hbox to include Notebook widget.
 * @param hbox This packing window will involve Notebook widget created.
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-5-19
 */
bool GlobalParameter::createNotebook(GtkWidget* hbox)
{
  GtkWidget* notebook = gtk_notebook_new();
  gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), FALSE);
  gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP);

  // Enable scrolling treeviwe 
  GtkWidget* scrolledwindow = gtk_scrolled_window_new(NULL,NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow),
								 GTK_POLICY_AUTOMATIC,
								 GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow),
									  GTK_SHADOW_IN);
  gtk_widget_set_size_request(GTK_WIDGET(m_hierarchyview),
							  NOTEBOOK_WIDTH,
							  NOTEBOOK_HEIGHT);
  gtk_widget_show(GTK_WIDGET(m_hierarchyview));

  gtk_container_add(GTK_CONTAINER(scrolledwindow),
					GTK_WIDGET(m_hierarchyview));
  
  gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
						   scrolledwindow,
						   gtk_label_new(HIERARCHY_VIEW_TAB_LABEL));
  gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
						   GTK_WIDGET(m_attributeview),
						   gtk_label_new(ATTRIBUTE_VIEW_TAB_LABEL));

  // Insert horizon packing
  gtk_box_pack_start(GTK_BOX(hbox), notebook, FALSE, FALSE, 0);

  gtk_widget_show(notebook);
  return true;
}

/**
 * Builting drawing window and rendering manager
 *
 * @param hbox This packing window will involve Notebook widget created.
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-5-20
 */
bool GlobalParameter::createDrawArea(GtkWidget* hbox)
{
  GdkGLConfig *glconfig;
  unsigned int mode = GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE;
  glconfig = gdk_gl_config_new_by_mode(static_cast<GdkGLConfigMode>(mode));

  if( glconfig == NULL ){
	cout << "Error Can not create draw area" << endl;
	return -1;
  }

  _drawarea = gtk_drawing_area_new();

  gtk_widget_set_events(_drawarea,
						gtk_widget_get_events(_drawarea)
						| GDK_BUTTON_PRESS_MASK
						| GDK_POINTER_MOTION_MASK
						);
  gtk_widget_set_gl_capability(_drawarea,
							   glconfig,
							   NULL,
							   TRUE,
							   GDK_GL_RGBA_TYPE);

  gtk_widget_set_size_request(_drawarea,
							  DRAW_AREA_WIDTH,
							  DRAW_AREA_HEIGHT);

  //Equip draw area with callback for resize events.
  g_signal_connect(G_OBJECT(_drawarea),
				   "size-allocate",
                   G_CALLBACK(cb_size_allocate), NULL);

  // Equip draw area with callback for mouse button pressed events.
  g_signal_connect(G_OBJECT(_drawarea),
				   "button_press_event",
				   G_CALLBACK(cb_button_press_event),
				   NULL);

  // Equip draw area with callback for mouse moved events.
  g_signal_connect(G_OBJECT(_drawarea),
				   "motion_notify_event",
				   G_CALLBACK(cb_motion_notify_event),
				   NULL);

  // Equip draw area with callback for mouse event
  g_signal_connect(G_OBJECT(_drawarea),
				   "scroll_event",
				   G_CALLBACK(cb_scroll_event),
				   NULL);

  g_signal_connect(G_OBJECT(_drawarea),
				   "expose_event",
				   G_CALLBACK(GlobalParameter::OnDraw),
				   NULL);

  g_signal_connect_after(G_OBJECT(_drawarea),
						 "realize",
						 G_CALLBACK(GlobalParameter::OnRealize),
						 NULL);

  // Creating rendering manager
  m_rm = new RenderingManager(library::log::LogManager::GetInstance());

  gtk_box_pack_start(GTK_BOX(hbox), _drawarea, TRUE, TRUE, 0);
}

/**
 * This function initialize attribute view window
 * \note
 * @param treeview attribute tree window
 * @param store 
 * @param flag enable triangle strip or not
 *
 * @author suzuki ryoutoku
 * @date 2008-1-7
 * @version 1.0
 */
bool GlobalParameter::initializeAttributeView(GtkTreeStore* store, 
											  bool flag)
{
  GtkTreeIter parent_iter;
  GtkTreeIter iter;
  // color
  gtk_tree_store_append(store, &parent_iter, NULL);
  gtk_tree_store_set(store, &parent_iter,0,"Color",-1);
  gtk_tree_store_append(store, &iter, &parent_iter);
  gtk_tree_store_set(store,&iter,0,"White",-1);
  gtk_tree_store_append(store, &iter, &parent_iter);
  gtk_tree_store_set(store,&iter,0,"Red",-1);
  gtk_tree_store_append(store, &iter, &parent_iter);
  gtk_tree_store_set(store,&iter,0,"Blue",-1);
  gtk_tree_store_append(store, &iter, &parent_iter);
  gtk_tree_store_set(store,&iter,0,"Green",-1);
  gtk_tree_store_append(store, &iter, &parent_iter);
  gtk_tree_store_set(store,&iter,0,"Grey",-1);
  gtk_tree_store_append(store, &iter, &parent_iter);
  gtk_tree_store_set(store,&iter,0,"Yellow",-1);
  gtk_tree_store_append(store, &iter, &parent_iter);
  gtk_tree_store_set(store,&iter,0,"Pink",-1);
  // draw method
  gtk_tree_store_append(store, &parent_iter, NULL);
  gtk_tree_store_set(store, &parent_iter,0,"Draw method",-1);
  gtk_tree_store_append(store, &iter, &parent_iter);
  gtk_tree_store_set(store,&iter,0,"Vertex array",-1);
  
  if( flag == true ){
	gtk_tree_store_append(store, &iter, &parent_iter);
	gtk_tree_store_set(store,&iter,0,"Triangle strip",-1);
	gtk_tree_store_append(store, &iter, &parent_iter);
	gtk_tree_store_set(store,&iter,0,"Strip with colors",-1);
  }
  return true;
}


/**
 * This method make model data initial state.
 * \note
 * After to load model data, Please call this function.
 *
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-6-25
 */
bool GlobalParameter::initializeModelData(void)
{
  this->m_model->getM_coordinate()->setM_pivot(Vector<double>(0.0,
															  0.0,
															  0.0,
															  0.0));
  this->m_rm->getM_screen(0)->registGraphicalCommand(m_model);
  this->m_rm->getM_screen(0)->getM_camera()->moveToFitModel(m_model);
  double depth;
  double height;
  double width;
  this->m_model->computeSize(&width,&height,&depth);
  if( width > height ){
	if( width > depth ){
	  this->setM_div(width/200.0);
	}else{
	  this->setM_div(depth/200.0);
	}
  }else{
	if( height > depth ){
	  this->setM_div(height/200.0);
	}else{
	  this->setM_div(depth/200.0);
	}
  }
  return true;
}

//=============================================================================
//	GlobalParameter's static memthod
//=============================================================================
/**
 * @return GlobalParameter::instance
 * @author suzuki ryoutoku
 * @date 2007-12-17
 * @version 1.0
 */
GlobalParameter* GlobalParameter::getInstance()
{
  return &(GlobalParameter::instance);
}

/**
 * This function is to create bitmapdata from jpg file
 * 
 * @param info This is set bitmap information.
 * @param bitpattern This is allocated memory and copied pixel pattern.
 * @param name This is filename.
 *
 * @author suzuki ryoutoku 
 * @date 2008-2-3
 * @version 1.0
 */
bool GlobalParameter::CreateBitmapFromJpgfile(BITMAPINFO* info,
											  unsigned int** bitpattern, 
											  const char* name)
{
  GError* error = NULL;
  GdkPixbuf* pixbuf = NULL;
  pixbuf = gdk_pixbuf_new_from_file(name,
									&error);
  if( pixbuf == NULL ){
	cout << "error->code:" << error->code << endl;
	cout << "error->message:" << error->message << endl;
	return false;
  }

#ifdef DEBUG
  // display informations of jpg 
  cout << "width = " << gdk_pixbuf_get_width(pixbuf) << endl;
  cout << "height = " << gdk_pixbuf_get_height(pixbuf) << endl;
  cout << "bits per sample = " << gdk_pixbuf_get_bits_per_sample(pixbuf)<<endl;
#endif

  guchar* pixel = gdk_pixbuf_get_pixels(pixbuf);
  int width = gdk_pixbuf_get_width(pixbuf);
  int height = gdk_pixbuf_get_height(pixbuf);
  int linewidth= gdk_pixbuf_get_rowstride(pixbuf);
  *bitpattern = new unsigned int[width*height];

  for( int i = 0; i < height; i ++ ){
	for( int j = 0; j < width; j ++ ){
	  unsigned int R = (int)(pixel[i*linewidth+j*3+0]);
	  unsigned int G = (int)(pixel[i*linewidth+j*3+1]);
	  unsigned int B = (int)(pixel[i*linewidth+j*3+2]);
	  unsigned int data = ((B << 16 )&0xFF0000)+((G << 8)&0xFF00)+R;
	  (*bitpattern)[(height-1-i)*width + j ] = data;
	}
  }
  info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  info->bmiHeader.biWidth = width;
  info->bmiHeader.biHeight = height;
  info->bmiHeader.biPlanes = 1;
  info->bmiHeader.biBitCount = 32;
  info->bmiHeader.biCompression = 0;
  info->bmiHeader.biSizeImage = 0;
  info->bmiHeader.biXPelsPerMeter = 0;
  info->bmiHeader.biYPelsPerMeter = 0;
  info->bmiHeader.biClrUsed= 0;
  info->bmiHeader.biClrImportant = 0;

  
  g_object_unref(pixbuf);
  return true;
}

/**
 *
 * @author suzuki ryoutoku
 * @version 1.0
 * @date 2008-11-29
 */
bool GlobalParameter::initializeTreeView(void)
{
  GtkTreeStore* store 
	= GTK_TREE_STORE(gtk_tree_view_get_model(m_hierarchyview));
  
  for(int i = 0; i < m_model->getPolygonBodySum(); i++ ){
	GtkTreeIter ite;
	library::graphics::PolygonBody* body = m_model->getPolygonBody(i);
	gtk_tree_store_append(store, &ite, NULL);//no parent
	gtk_tree_store_set(store, 
					   &ite,
					   0,
					   GL_TRUE,
					   1,
					   body->get_name(),
					   -1);
	GtkTreeIter childite;
	int groupsum = body->querySpecificGroupSum(Group::TEXTURE, Group::CLUSTER);
	for(int j=0; j < groupsum; j++){
	  Group* group = body->queryGroup(Group::TEXTURE, Group::CLUSTER, j);
	  gtk_tree_store_append(store, &childite, &ite);
	  gtk_tree_store_set(store, 
						 &childite,
						 0,GL_TRUE,
						 1,
						 group->get_name(),
						 -1);
	}
  }
}


/**
 * When window catched drawing events, Calling this function.
 * @author suzuki ryoutoku
 * @date 2010-8-2
 * @version 1.0
 */
gint GlobalParameter::OnDraw(GtkWidget* widget, 
							 GdkEventExpose* event, 
							 gpointer user_data)
{
  GdkGLContext *glcontext = gtk_widget_get_gl_context(widget);
  GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(widget);
  timeval tv_before;
  timeval tv_after;
  char tmp[256];

  if( !gdk_gl_drawable_gl_begin(gldrawable, glcontext) ) FALSE;

  gettimeofday(&tv_before, NULL);

  instance.m_rm->draw();
  gdk_gl_drawable_swap_buffers(gldrawable);
  gettimeofday(&tv_after, NULL);
  
  sprintf(tmp, 
		  "fps = %f", 
		  1000000.0f/(float)(tv_after.tv_usec - tv_before.tv_usec));
  instance.getM_fps()->setM_message(tmp);
}


/**
 * Creating fonts data
 * @author r-suzuki
 * @date 2010-8-2
 * @version 1.0
 */
void GlobalParameter::OnRealize(GtkWidget *widget, gpointer   data)
{
  GdkGLContext* glcontext = gtk_widget_get_gl_context(widget);
  GdkGLDrawable* gldrawable = gtk_widget_get_gl_drawable(widget);
  PangoFontDescription* font_desc;
  PangoFont* font;
  PangoFontMetrics* font_metrics;
  char font_string[] = "courier 12";

  if( !gdk_gl_drawable_gl_begin(gldrawable, glcontext) ){
    return;
  }
  
  // Creating display lists
  instance.m_rm->setM_font_number(glGenLists(128));

  font_desc = pango_font_description_from_string(font_string);

  font = gdk_gl_font_use_pango_font(font_desc, 
									0, 
									128, 
									instance.m_rm->getM_font_number());

  if( font == NULL ){
	cout << "Error load font" << endl;
	return;
  }

  font_metrics = pango_font_get_metrics(font, NULL);

  int font_height = pango_font_metrics_get_ascent(font_metrics)
	+ pango_font_metrics_get_descent(font_metrics);

  font_height = PANGO_PIXELS(font_height);

  pango_font_description_free(font_desc);
  pango_font_metrics_unref(font_metrics);

  glClearColor (1.0, 1.0, 1.0, 1.0);
  glClearDepth (1.0);

  glViewport (0, 0,
              widget->allocation.width, widget->allocation.height);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho (0.0, widget->allocation.width,
           0.0, widget->allocation.height,
           -1.0, 1.0);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();

  gdk_gl_drawable_gl_end (gldrawable);
}

//=============================================================================
//	GlobalParameter's static member
//=============================================================================
/**
 * unique instance
 * @author suzuki ryoutoku
 * @date 2007-12-17
 * @version 1.0
 */
GlobalParameter GlobalParameter::instance;

